//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include <drvpub.h>
#include "Mutex.h"

#define DBGDOC(l, s)    //s

#ifdef __cplusplus
extern "C" {
#endif

#define   SECSZ       512

// control code for ftl level
typedef enum FTLControl {
    FTLControl_GetSize      = 0x1,
    FTLControl_GetCapacity  = 0x10000000,
    FTLControl_DeleteBlock  = 0x10000001,
    FTLControl_Mount        = 0x10000002,
    FTLControl_Unmount      = 0x10000003,
    FTLControl_DFormat      = 0x10000004,

    //Reserved for backup/restore tools
    FTLControl_InitReadBinaryPart  = 0x10000005,
    FTLControl_ReadBinaryPart      = 0x10000006,
    FTLControl_InitWriteBinaryPart = 0x10000008,
    FTLControl_WriteBinaryPart     = 0x10000009,

    FTLControl_GetUniqueId         = 0x1000000a,
    FTLControl_InsertProtectKey    = 0x1000000b,

    FTLControl_InitReadSPL  = 0x1000000c,
    FTLControl_ReadSPL      = 0x1000000d,
    FTLControl_InitWriteSPL = 0x1000000e,
    FTLControl_WriteSPL     = 0x1000000f,

    FTLControl_ReadIPL      = 0x10000010,
    FTLControl_WriteIPL     = 0x10000011,

    FTLControl_ReadLogo     = 0x10000012,
    FTLControl_WriteLogo     = 0x10000013,

    FTLControl_ReadTatTable  = 0x10000014,
    FTLControl_WriteTatTable = 0x10000015,

} FTLControl;

typedef struct ProtectKey {
    int uPartitionNo;
    unsigned char chKey[8];
} ProtectKey;

typedef struct FlashInfo {
    UInt32 Size;
    UInt32 EraseUnitSize;
} FlashInfo;

typedef enum FlashControl {
    FlashControl_Open               = 0,
    FlashControl_EraseSector,
    FlashControl_GetInfo,
    FlashControl_MapToUserSpace
} FlashControl;

Address doc_VirtBase = 0;
Address doc_HeapBase = 0;
UInt32 doc_HeapSize = 200000;

//global atomic flag

int doc_read(int part, unsigned char *buffer, unsigned long sector, int nblocks);
int doc_write(int part, unsigned char *buffer, unsigned long sector, int nblocks);
int doc_delete(int part, unsigned long sector, int nblocks);
int doc_dformat(int part);
int doc_mount(int part);
int doc_unmount(int part);
unsigned int doc_getsize(int part);
unsigned int doc_getbootsec(int part);
int InitReadBinaryPart(unsigned int PartitionSize);
int ReadBinaryPart(unsigned long to, unsigned int bufSize);
int InitWriteBinaryPart(unsigned int PartitionSize);
int WriteBinaryPart(unsigned long from, unsigned int bufSize);
unsigned int doc_getuid(unsigned char * buffer);
void doc_powerdown();
void doc_resume();
int doc_info(void);
unsigned int doc_getcapacity();
int InsertProtectKey(int uPartitionNo, unsigned char *pKey);
int InitReadSPL(unsigned int PartitionSize);
int ReadSPL(unsigned char * to, unsigned int bufSize);
int ReadLogo(unsigned char * ptr, unsigned int size);
int WriteLogo(unsigned char * ptr, unsigned int size);
int InitWriteSPL(unsigned int PartitionSize);
int WriteSPL(unsigned char * to, unsigned int bufSize);
int ReadIPL(unsigned char * ptr);
int WriteIPL(unsigned char * ptr);
int ReadTatTable(unsigned char * ptr, unsigned int size);
int WriteTatTable(unsigned char * ptr, unsigned int size);

void udelay(int ms)
{
    DzDelay(ms * 1000);
}

#ifdef __cplusplus
}
#endif

// Each doc partion corresponds to a minor device
class BlkDoc : public CDeviceDriver {
public:
    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf &buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf &inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    ECode DeleteBlock(
        /* [in] */ UInt32 sector,
        /* [in] */ UInt32 nblocks);

    BlkDoc::BlkDoc();
    virtual void Dispose() {};

public:
    friend IDeviceDriver * CDECL CreateDocStorage(uint_t uDeviceNo, void *pvParameter);

private:
    Boolean IsMount;
    int Part;
    unsigned int StartAddr;
    unsigned int Size;
};

//LNOTENOTE: use global mutex, for there is only one physical DiskOnChip device
    DzMutex Wait;
    WaitResult wr;
    int m_drvstat;

BlkDoc::BlkDoc()
{
    IsMount = FALSE;
    Part = -1/*INVALIDHANDLE*/;
    Size = 0;
    m_drvstat = -1;
}

ECode BlkDoc::Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    if (!IsMount) return E_ACCESS_DENIED;

    UInt32 offset = (UInt32)u64Offset;
    if (pBuffer == NULL || !pBuffer->GetCapacity()
        || (offset & 511)
        || (bytesToRead & 511)
        || (!((StartAddr <= offset) && ((offset + bytesToRead) <= (StartAddr + Size))))
        ) {
        DBGDOC(0, cprintf("Invailed Read Argument!\n"));
        return E_INVALID_ARGUMENT;
    }

    NotifyByIsr(EVENT_BUS_POWER, 0x1<<16, NULL);

    Byte *pBuf = pBuffer->GetPayload();
    Byte buffer[SECSZ];
    UInt32 sector,leftsize,rightsize;
    int ret,nblocks, sizeRead;

    sector = RoundUp(offset, SECSZ) >> 9;
    nblocks = ((offset + bytesToRead)>>9) - sector;
    if (nblocks < 0) nblocks=0;
    rightsize = (offset + bytesToRead) % SECSZ;
    offset = offset % SECSZ;
    leftsize = (offset)?SECSZ - offset:0;

    DBGDOC(0, cprintf("Doc:Read: sector=%x, nblocks=%x\n", sector, nblocks));

    sizeRead = 0;
    ret = NOERROR;

    Wait.Lock(&wr);
    if (m_drvstat == 1) {
        cprintf("ERR: DOC Storage can't be accessed! Debug in BlkDoc::Read!\n");
        DebugBreak();
    }
    m_drvstat = 1;

    if (nblocks) {

        if ( (const int)(!(Address)(pBuf + leftsize)) & 3) {
            ret = doc_read(Part, pBuf+leftsize, sector, nblocks);
            if (ret) goto Done;
        }
        else {
            int i;
            for(i=0;i<nblocks;i++) {
                ret = doc_read(Part,buffer,sector+i,1);
                if (ret) goto Done;
                memcpy(pBuf+leftsize+(i<<9),buffer,SECSZ);
            }
        }
        sizeRead += nblocks << 9;
    }
    if (offset) {
        if (bytesToRead < (Int32)leftsize) {
            leftsize = bytesToRead;
            rightsize = 0;
        }
        ret = doc_read(Part, buffer, sector-1, 1);
        if (ret) goto Done;
        memcpy(pBuf,buffer+offset,leftsize);
        sizeRead += leftsize;

    }
    if (rightsize) {
        ret = doc_read(Part, buffer, sector+nblocks, 1);
        if (ret) goto Done;
        memcpy(pBuf+bytesToRead-rightsize, buffer, rightsize);
        sizeRead += rightsize;
    }

Done:
    m_drvstat = 0;
    Wait.Unlock();
    if (!ret) {
        pBuffer->SetUsed(sizeRead);
        return NOERROR;
    }
    else
        return E_UNEXPECTED;
}

ECode BlkDoc::Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf &buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    if (!IsMount) return E_ACCESS_DENIED;

    UInt32 offset, sizeToWrite;
    offset = (UInt32)u64Offset;
    sizeToWrite = (UInt32)buffer.GetUsed();
    if (!buffer.GetCapacity()
        || (0 == sizeToWrite)
        || (offset & 511)
        || (sizeToWrite & 511)
        || (!((StartAddr <= offset) && ((offset + sizeToWrite) <= (StartAddr + Size))))
//        || (NULL == pBytesWritten)
        ) {
        DBGDOC(0, cprintf("Invailed Write Argument!\n"));
        return E_INVALID_ARGUMENT;
    }

    NotifyByIsr(EVENT_BUS_POWER, 0x1<<16, NULL);

    Byte byteBuffer[SECSZ];
    UInt32 sector,leftsize,rightsize,len;
    int ret,nblocks;
    Byte *pBuf = buffer.GetPayload();

    sector = RoundUp(offset, SECSZ) >> 9;
    nblocks = ((offset + sizeToWrite)>>9) - sector;
    if (nblocks < 0) nblocks=0;
    rightsize = (offset + sizeToWrite) % SECSZ;
    offset = offset % SECSZ;
    leftsize = (offset)?SECSZ - offset:0;

    DBGDOC(1, cprintf("Doc:Write: sector=%x, nblocks=%x\n", sector, nblocks));

    len = 0;
    ret = NOERROR;

    Wait.Lock(&wr);
    if (m_drvstat == 1) {
        cprintf("ERR: DOC Storage can't be accessed! Debug in BlkDoc::Write!\n");
        DebugBreak();
    }
    m_drvstat = 1;

    if (nblocks) {

        if ( (const int)(!(Address)(pBuf + leftsize)) & 3) {

            ret = doc_write(Part, (Byte*)(pBuf+leftsize), sector, nblocks);
            if (ret) goto Done;
        }
        else {
            int i;
            for(i=0;i<nblocks;i++) {
                memcpy(byteBuffer,pBuf+leftsize+(i<<9),SECSZ);
                ret = doc_write(Part,byteBuffer,sector+i,1);
                if (ret) goto Done;
            }
        }
        len += nblocks << 9;
    }
    if (offset) {
        if (sizeToWrite < leftsize) {
            leftsize = sizeToWrite;
            rightsize = 0;
        }
        ret = doc_read(Part, byteBuffer, sector-1, 1);
        if (ret) goto Done;
        memcpy(byteBuffer+offset,pBuf,leftsize);
        ret = doc_write(Part, byteBuffer, sector-1, 1);
        if (ret) goto Done;

        len += leftsize;

    }
    if (rightsize) {
        ret = doc_read(Part, byteBuffer, sector+nblocks, 1);
        if (ret) goto Done;
        memcpy(byteBuffer, pBuf+sizeToWrite-rightsize, rightsize);
        ret = doc_write(Part, byteBuffer, sector+nblocks, 1);
        if (ret) goto Done;
        len += rightsize;
    }

Done:
    m_drvstat = 0;
    Wait.Unlock();
    if (!ret) {
        if (pBytesWritten)
            *pBytesWritten = len;
        return NOERROR;
    }
    else {
        return E_UNEXPECTED;
    }
}

ECode BlkDoc::Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf &inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec = E_ACCESS_DENIED;
    DBGDOC(2, cprintf("Doc:Control: [%x]\n", nControlCode));

    switch(nControlCode) {
        case FTLControl_GetSize: {
            if (pOutBuffer == NULL ||
                pOutBuffer->GetCapacity() < (Int32)sizeof(int)) {
                return E_INVALID_ARGUMENT;
            }
            *(int *)pOutBuffer->GetPayload() = Size;
            pOutBuffer->SetUsed(sizeof(int));
            return NOERROR;
            break;
        }
        case FTLControl_GetCapacity: {
            if (pOutBuffer == NULL ||
                pOutBuffer->GetCapacity() < (Int32)sizeof(int)) {
                return E_INVALID_ARGUMENT;
            }
            *(int *)pOutBuffer->GetPayload() = doc_getcapacity();
            pOutBuffer->SetUsed(sizeof(int));
            return NOERROR;
            break;
        }
        case FTLControl_DeleteBlock: {
            if (!IsMount) return E_ACCESS_DENIED;

            NotifyByIsr(EVENT_BUS_POWER, 0x1<<16, NULL);

            UInt32 sector=0;
            UInt32 nblocks=0;
            memcpy((char *)&sector, inBuffer.GetPayload(), 4);
            memcpy((char *)&nblocks, ((char *)inBuffer.GetPayload())+4, 4);
            return DeleteBlock(sector, nblocks);
            break;
        }
        case FTLControl_Mount: {
            if (IsMount) return E_ACCESS_DENIED;
            if (0 == doc_mount(Part)) {
                IsMount = TRUE;
                return NOERROR;
            }
            else return E_FAIL;
            break;
        }
        case FTLControl_Unmount: {
            if (!IsMount) return E_ACCESS_DENIED;
            if (0 == doc_unmount(Part)) {
                IsMount = FALSE;
                return NOERROR;
            }
            else return E_FAIL;
            break;
        }
        case FTLControl_DFormat: {//DFormat Low Level
            if (IsMount) {
                doc_unmount(Part);
                IsMount = FALSE;
            }
            if (0 == doc_dformat(Part)) {
                return NOERROR;
            }
            else return E_FAIL;
            break;
        }
        case FTLControl_InitReadBinaryPart: {
            if (!IsMount) return E_ACCESS_DENIED;

            unsigned int size;
            memcpy((char *)&size, inBuffer.GetPayload(), 4);
            InitReadBinaryPart(size);
            ec = NOERROR;
            break;
        }
        case FTLControl_ReadBinaryPart: {
            if (!IsMount) return E_ACCESS_DENIED;

            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            Byte *pBuf = pOutBuffer->GetPayload();
            pOutBuffer->SetUsed(pOutBuffer->GetCapacity());
            ReadBinaryPart((unsigned long)pBuf, pOutBuffer->GetCapacity());
            ec = NOERROR;
            break;
        }
        case FTLControl_InitWriteBinaryPart: {
            if (IsMount) return E_ACCESS_DENIED;

            unsigned int size;
            memcpy((char *)&size, inBuffer.GetPayload(), 4);
            InitWriteBinaryPart(size);
            ec = NOERROR;
            break;
        }
        case FTLControl_WriteBinaryPart: {
            if (IsMount) return E_ACCESS_DENIED;

            Byte *pBuf = inBuffer.GetPayload();
            WriteBinaryPart((unsigned long)pBuf, inBuffer.GetCapacity());
            ec = NOERROR;
            break;
        }
        case FTLControl_GetUniqueId: {
            if (!IsMount) return E_ACCESS_DENIED;
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            Byte *pBuf = pOutBuffer->GetPayload();
            pOutBuffer->SetUsed(pOutBuffer->GetCapacity());
            doc_getuid(pBuf);
            ec = NOERROR;
            break;
        }
        case FTLControl_InsertProtectKey: {
            assert(NULL != inBuffer.GetCapacity());
            ProtectKey key;
            memcpy((char*)&key, inBuffer.GetPayload(), sizeof(ProtectKey));
            if (InsertProtectKey(key.uPartitionNo, key.chKey)) {
                ec = E_FAIL;
            }
            else {
                ec = NOERROR;
            }
            break;
        }
        case FTLControl_InitReadSPL: {
            if (!IsMount) return E_ACCESS_DENIED;
            unsigned int size;
            memcpy((char *)&size, inBuffer.GetPayload(), 4);
            InitReadSPL(size);
            ec = NOERROR;
            break;
        }
        case FTLControl_ReadSPL: {
            if (!IsMount) return E_ACCESS_DENIED;
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            Byte *pBuf = pOutBuffer->GetPayload();
            pOutBuffer->SetUsed(pOutBuffer->GetCapacity());
            ReadSPL(pBuf, pOutBuffer->GetCapacity());
            ec = NOERROR;
            break;
        }
        case FTLControl_InitWriteSPL: {
            if (IsMount) return E_ACCESS_DENIED;
            unsigned int size;
            memcpy((char *)&size, inBuffer.GetPayload(), 4);
            InitWriteSPL(size);
            ec = NOERROR;
            break;
        }
        case FTLControl_WriteSPL: {
            if (IsMount) return E_ACCESS_DENIED;
            Byte *pBuf = inBuffer.GetPayload();
            WriteSPL(pBuf, inBuffer.GetCapacity());
            ec = NOERROR;
            break;
        }
         case FTLControl_ReadIPL: {
            if (!IsMount) return E_ACCESS_DENIED;
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            Byte *pBuf = pOutBuffer->GetPayload();
            pOutBuffer->SetUsed(pOutBuffer->GetCapacity());
            ReadIPL(pBuf);
            ec = NOERROR;
            break;
        }
        case FTLControl_WriteIPL: {
            if (IsMount) return E_ACCESS_DENIED;
            Byte *pBuf = inBuffer.GetPayload();
            WriteIPL(pBuf);
            ec = NOERROR;
            break;
        }
        case FTLControl_ReadLogo: {
            if (!IsMount) return E_ACCESS_DENIED;
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            Byte *pBuf = pOutBuffer->GetPayload();
            pOutBuffer->SetUsed(pOutBuffer->GetCapacity());
            ReadLogo(pBuf, pOutBuffer->GetCapacity());
            ec = NOERROR;
            break;
        }
        case FTLControl_WriteLogo: {
            if (!IsMount) return E_ACCESS_DENIED;
            Byte *pBuf = inBuffer.GetPayload();
            WriteLogo(pBuf,inBuffer.GetCapacity());
            ec = NOERROR;
            break;
        }
        case FTLControl_ReadTatTable: {
            Wait.Lock(&wr);
            if (m_drvstat == 1) {
                cprintf("ERR: DOC Storage can't be accessed! Debug in BlkDoc::ReadTatTable!\n");
                DebugBreak();
            }
            m_drvstat = 1;
            if (pOutBuffer == NULL) {
                return E_INVALID_ARGUMENT;
            }
            Byte *pBuf = pOutBuffer->GetPayload();
            int ret = ReadTatTable(pBuf, pOutBuffer->GetCapacity());
            if (ret == 0) ec = E_FAIL;
            else {
                pOutBuffer->SetUsed(pOutBuffer->GetCapacity());
                ec = NOERROR;
            }
            m_drvstat = 0;
            Wait.Unlock();
            break;
        }
        case FTLControl_WriteTatTable: {
            Wait.Lock(&wr);
            if (m_drvstat == 1) {
                cprintf("ERR: DOC Storage can't be accessed! Debug in BlkDoc::WriteTatTable!\n");
                DebugBreak();
            }
            m_drvstat = 1;
            Byte *pBuf = inBuffer.GetPayload();
            int ret = WriteTatTable(pBuf, inBuffer.GetCapacity());
            if (ret == 0) ec = E_FAIL;
            else ec = NOERROR;
            m_drvstat = 0;
            Wait.Unlock();
            break;
        }
        case 1000:
            //cprintf("++++++++++++++++++DOC suspend++++++++++++++++++\n");
            //doc_powerdown();
            ec = NOERROR;
            break;
        case 1001:
            //cprintf("++++++++++++++++++DOC resume++++++++++++++++++\n");
            //doc_resume();
            ec = NOERROR;
            break;
        case 1002:
            //cprintf("++++++++++++++++++DOC Wake up++++++++++++++++++\n");
            doc_resume();
            ec = NOERROR;
            break;
        case 1003:
            //cprintf("++++++++++++++++++DOC Power down++++++++++++++++++\n");
            doc_powerdown();
            ec = NOERROR;
            break;
        default:
            break;
    }
    return ec;
}

ECode BlkDoc::DeleteBlock(
        /* [in] */ UInt32 sector,
        /* [in] */ UInt32 nblocks)
{
    int ret;
    Wait.Lock(&wr);
    if (m_drvstat == 1) {
        cprintf("ERR: DOC Storage can't be accessed! Debug in BlkDoc::DeleteBlock!\n");
        DebugBreak();
    }
    m_drvstat = 1;

    ret = doc_delete(Part, sector, nblocks);

    m_drvstat = 0;
    Wait.Unlock();

    if (!ret) {
        return NOERROR;
    }
    else {
        return E_UNEXPECTED;
    }
}

void DocWake()
{
    NotifyByIsr(EVENT_BUS_POWER, 0x1<<16, NULL);
}

#define DOC_CHIP_ID_REG         0x1000  /* READ */
#define FLREAD_IO_WORD(address) (*((volatile unsigned short *)(address)))
int IsG4DocType(Address base)
{
    unsigned short tmp = FLREAD_IO_WORD(base+DOC_CHIP_ID_REG);
    tmp = FLREAD_IO_WORD(base+DOC_CHIP_ID_REG);
    return 0x0400==FLREAD_IO_WORD(base+DOC_CHIP_ID_REG);
}

EXTERN IDeviceDriver * CDECL CreateDocStorage(uint_t uDeviceNo, void *pvParameter)
{
    int Part = (int)uDeviceNo;
    int PageOrder = 6;
    //Create a new doc block device
    BlkDoc *pBlkDoc = new BlkDoc();
    if (NULL == pBlkDoc) goto ErrorExit;
    pBlkDoc->AddRef();

    //Map doc window virtual base
    if (doc_VirtBase == 0) {
        doc_VirtBase = (Address)DzKIoRemap(0, 1024*1024/*Only need 2k*/);
        if(IsG4DocType(doc_VirtBase)){
                PageOrder = 7;
        }
    }
    if (doc_HeapBase == 0) {
        //doc_HeapBase = (Address)Drv_AllocBuffer(200/*min 200k*/, 0);
        doc_HeapBase = (Address)DzAllocKernelPages(PageOrder, 0);
        doc_HeapSize = 4*1024<<PageOrder;//FL_TRUEFFS_HEAP_SIZE
    }
    cprintf("Create DOC Storage OK, WinBase [0x%08x], [0x%08x], [0x%08x]\n",
        doc_VirtBase, doc_HeapBase, doc_HeapSize);
    assert(0 != doc_VirtBase);
    assert(0 != doc_HeapBase);

    //Mount the doc
    //assert(Part == 0 || Part == 1);
    if (doc_mount(Part) != 0) goto ErrorExit;

    //Get part size

    //Setup device property
    pBlkDoc->Part = Part;
    pBlkDoc->StartAddr = doc_getbootsec(Part);
    pBlkDoc->Size = doc_getsize(Part);
    pBlkDoc->IsMount = TRUE;

    return pBlkDoc;

ErrorExit:
    if (pBlkDoc) delete pBlkDoc;
    return NULL;
}
